﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using devtm.Aop.Extensions;
using Mono.Cecil;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Ast;
using ICSharpCode.Decompiler.ILAst;
using System.IO;
using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil.Cil;
using devtm.Aop.Event;
using devtm.Aop;

namespace devtm.Aop.InternalProcess.Decompiling
{

    public static class CodeMethodHelper
    {


        public static string GetCode(this Exception exception, bool html, out int start, out int end, out int line)
        {
            StackTrace s = new StackTrace(exception);
            return GetCode(s, html, out start, out end, out line);
        }


        public static string GetCode(this StackTrace stack, bool html, out int start, out int end, out int line)
        {
            var f = stack.GetFrame(0);
            return GetCode(f, html, out start, out end, out line);
        }


        public static string GetCode(this StackFrame frame, bool html, out int start, out int end, out int line)
        {          
            MethodBase m = frame.GetMethod();
            Int32 offset = frame.GetILOffset();
            return GetCode(m, offset, html, out start, out end, out line);
        }


        public static string GetCode(this MethodBase m, Int32 offset, bool html, out int start , out int end, out int line)
        {

            DecompilerContext context;
            ILAstBuilder ils;
            List<ILNode> operations;

            var t = m.DeclaringType.Assembly.Location;
            
            // On charge le module
            var u = t.LoadAssemblyMono();

            // On initialise toutes les classes
            ModuleDefinition _module = u.MainModule;
            MethodDefinition method = _module.Import(m).Resolve();
            TypeDefinition source = method.DeclaringType;


            context = new DecompilerContext(_module);
            AstBuilder builder = new AstBuilder(new DecompilerContext(_module))
            {
                DecompileMethodBodies = true
            };

            builder.AddType(source);
            context.CurrentType = source;
            
            ils = new ILAstBuilder();
            operations = ils.Build(method, true);
            
            BlockStatement iop = AstMethodBodyBuilder.CreateMethodBody(method, context);

            StringWriter w = new StringWriter();

            var t1 = new OutputVisitor2(w, new CSharpFormattingOptions(), html, offset);

            WriteEnteteMethod(t1.Formatter, method, html);

            iop.AcceptVisitor(t1, null);

            start = t1.Formatter.Start;
            end = t1.Formatter.End;

            string code = w.ToString();

            line = code.Substring(0, start).Count(c => c == '\n');

            string[] tb = code.Split('\n');
            int index = 1;

            code = WriteNumber(index);
            foreach (var item in tb)
            {
               
                code += item;
                index++;

                if (!string.IsNullOrEmpty(item))
                {
                    code += WriteNumber(index);
                }
            }
            
            index--;

            start = start + (line * 6);

            line++;

            return code;

        }


        private static string WriteNumber(int index)
        {
            string i = index.ToString();

            if (i.Length == 1)            
                i = "00" + i;

            else if (i.Length == 2)
                i = "0" + i;

            return i + " : ";

        }


        private static void WriteEnteteMethod(TextWriterOutputFormatter2 w, MethodDefinition m, bool html)
        {

            bool ispublic = (m.Attributes & Mono.Cecil.MethodAttributes.Public) == Mono.Cecil.MethodAttributes.Public;
            bool isPrivate = (m.Attributes & Mono.Cecil.MethodAttributes.Private) == Mono.Cecil.MethodAttributes.Private;
            bool isInternalProtected = (m.Attributes & Mono.Cecil.MethodAttributes.FamANDAssem) == Mono.Cecil.MethodAttributes.FamANDAssem;
            bool isProtected = (m.Attributes & Mono.Cecil.MethodAttributes.Family) == Mono.Cecil.MethodAttributes.Family;            
            if (ispublic)
                w.WriteKeyword("public");
            else            
                w.WriteKeyword("private");
            w.Space();


            bool isStatic = (m.Attributes & Mono.Cecil.MethodAttributes.Static) == Mono.Cecil.MethodAttributes.Static;
            if (isStatic)
            {
                w.WriteKeyword("static");
                w.Space();
            }

            bool isVirtual = (m.Attributes & Mono.Cecil.MethodAttributes.Virtual) == Mono.Cecil.MethodAttributes.Virtual;
            if (isVirtual)
            {
                w.WriteKeyword("virtual");
                w.Space();
            }

            string name = m.ReturnType.Resolve().FullName;
            if (name == "System.Void")
                name = "void";
            if (OutputVisitor2.IsKeyword(name))
                w.WriteKeyword(name);
            else
                w.WriteIdentifier(name);
            w.Space();


            w.WriteIdentifier(m.Name.Replace(InterceptEventArgsHelper.BaseExtendedName, ""));

            if (m.HasGenericParameters)
            {
                w.WriteToken("<");
                bool a = false;
                foreach (GenericParameter gp in m.GenericParameters)
                {

                    if (a == true)
                    {
                        w.WriteToken(",");
                        w.Space();
                    }

                    w.WriteIdentifier(gp.Name);

                    a = true;

                }
                w.WriteToken(">");
            }


            w.WriteToken("(");
            if (m.HasParameters)
            {
                bool a = false;
                foreach (ParameterDefinition p in m.Parameters)
                {

                    if (a == true)
                    {
                        w.WriteToken(",");
                        w.Space();
                    }

                    string nn = p.ParameterType.Name;
                    nn = Convert(nn);
                    if (OutputVisitor2.IsKeyword(nn))
                        w.WriteKeyword(nn);
                    else
                        w.WriteIdentifier(p.ParameterType.FullName);

                    w.Space();

                    //if (string.IsNullOrEmpty(p.Name))
                    //    p.Name = "arg" + p.Index.ToString();

                    w.WriteIdentifier(p.Name);

                    a = true;
                }

            }

            w.WriteToken(")");
            w.NewLine();


        }

        private static string Convert(string nn)
        {

            if (nn == "Int32")
                return "int";

            if (nn == "Int16")
                return "short";

            if (nn == "Int64")
                return "long";

            string n = nn.ToLower();
            if (OutputVisitor2.IsKeyword(n))
                return n;

            return nn;

        }

    }
}
